#ifndef INCLUDE__AST_H
#define INCLUDE__AST_H

#include "utils.h"
#include "symbol.h"

typedef struct ast_decl_s *ast_decl_t;
typedef struct ast_stmt_s *ast_stmt_t;
typedef struct ast_expr_s *ast_expr_t;
typedef struct ast_type_s *ast_type_t;

enum ast_primitive_type_e
{
		AST_INT, AST_FLOAT, AST_CHAR, AST_STRING, AST_BOOLEAN,
};

enum ast_binop_e
{
		AST_AND, AST_OR,
		AST_LT, AST_LE, AST_GT, AST_GE,
		AST_EQ, AST_NEQ,
		AST_PLUS, AST_MINUS, AST_TIMES, AST_DIVIDE,
};

enum ast_literal_e
{
		AST_INT_LIT, AST_FLOAT_LIT, AST_CHAR_LIT,
		AST_STRING_LIT, AST_BOOLEAN_LIT,
};

typedef enum ast_primitive_type_e ast_primitive_type_t;
typedef enum ast_binop_e ast_binop_t;
typedef enum ast_literal_e ast_literal_t;

struct ast_decl_s
{
		enum
		{
				AST_CLASS_DECL, AST_VAR_DECL,
				AST_METHOD_DECL, AST_PARA_DECL,
		} kind;
		int pos;
		union
		{
				struct { ast_type_t name; list_t var_decls; list_t method_decls; } class_;
				struct { ast_type_t type; symbol_t name; } var;
				struct { ast_type_t type; symbol_t name;
									list_t para_decls; list_t var_decls; list_t stmts;
									ast_expr_t ret; } method;
				struct { ast_type_t type; symbol_t name; } para;
		} u;
};
ast_decl_t ast_class_decl(int pos, ast_type_t name, list_t var_decls, list_t method_decls);
ast_decl_t ast_var_decl(int pos, ast_type_t type, symbol_t name);
ast_decl_t ast_method_decl(int pos, ast_type_t type, symbol_t name, list_t para_decls,
														list_t var_decls, list_t stmts, ast_expr_t ret);
ast_decl_t ast_para_decl(int pos, ast_type_t type, symbol_t name);

void print_ast_decl(ast_decl_t r, int level);
void print_ast_class_decl(ast_decl_t r, int level);
void print_ast_var_decl(ast_decl_t r, int level);
void print_ast_method_decl(ast_decl_t r, int level);
void print_ast_para_decl(ast_decl_t r, int level);


struct ast_stmt_s
{
		enum
		{
				AST_STMTS_STMT, AST_IF_STMT,
				AST_WHILE_STMT, AST_VAR_ASSIGN_STMT,
				AST_ARR_ASSIGN_STMT, AST_CALL_STMT,
		} kind;
    int pos;
		union
		{
				list_t stmts;
				struct { ast_expr_t cond; ast_stmt_t if_block; ast_stmt_t else_block; } if_;
				struct { ast_expr_t cond; ast_stmt_t block; } while_;
				struct { symbol_t lhs; ast_expr_t rhs; } var_assign;
				struct { symbol_t lhs_id; ast_expr_t lhs_index; ast_expr_t rhs; } arr_assign;
				struct { ast_expr_t field; symbol_t name; list_t args; } call;
		} u;
};
ast_stmt_t ast_stmts_stmt(int pos, list_t stmts);
ast_stmt_t ast_if_stmt(int pos, ast_expr_t cond, ast_stmt_t if_block,
												ast_stmt_t else_block);
ast_stmt_t ast_while_stmt(int pos, ast_expr_t cond, ast_stmt_t block);
ast_stmt_t ast_var_assign_stmt(int pos, symbol_t lhs, ast_expr_t rhs);
ast_stmt_t ast_arr_assign_stmt(int pos, symbol_t lhs_id,
												ast_expr_t lhs_index, ast_expr_t rhs);
ast_stmt_t ast_call_stmt(int pos, ast_expr_t field, symbol_t name, list_t args);

void print_ast_stmt(ast_stmt_t r, int level);
void print_ast_stmts_stmt(ast_stmt_t r, int level);
void print_ast_if_stmt(ast_stmt_t r, int level);
void print_ast_while_stmt(ast_stmt_t r, int level);
void print_ast_var_assign_stmt(ast_stmt_t r, int level);
void print_ast_arr_assign_stmt(ast_stmt_t r, int level);
void print_ast_call_stmt(ast_stmt_t r, int level);

struct ast_expr_s
{
		enum
		{
				AST_OP_EXPR, AST_NOT_EXPR,
				AST_ARR_ACCESS_EXPR, AST_METHOD_ACCESS_EXPR,
				AST_ARR_LENGTH_EXPR, AST_IDF_EXPR, AST_THIS_EXPR,
				AST_CLASS_INIT_EXPR, AST_ARR_INIT_EXPR,
				AST_ARR_DECL_EXPR, AST_INT_EXPR, AST_FLOAT_EXPR,
				AST_CHAR_EXPR, AST_STRING_EXPR, AST_BOOL_EXPR,
		} kind;
		int pos;
		union
		{
				struct { ast_expr_t left; ast_binop_t op; ast_expr_t right; } op;
				ast_expr_t not_;
				struct { ast_expr_t arr; ast_expr_t index; } arr_access;
				struct { ast_expr_t field; symbol_t name; list_t args; } method_access;
				ast_expr_t arr_length;
				symbol_t idf;
				symbol_t class_init;
				struct { ast_type_t type; ast_expr_t size; } arr_init;
				list_t arr_decl;
				int ival;
				float fval;
				char cval;
				string_t sval;
				bool bval;
		} u;
};
ast_expr_t ast_op_expr(int pos, ast_expr_t left, ast_binop_t op, ast_expr_t right);
ast_expr_t ast_not_expr(int pos, ast_expr_t expr);
ast_expr_t ast_arr_access_expr(int pos, ast_expr_t arr, ast_expr_t index);
ast_expr_t ast_method_access_expr(int pos, ast_expr_t field, symbol_t name, list_t args);
ast_expr_t ast_arr_length_expr(int pos, ast_expr_t arr);
ast_expr_t ast_idf_expr(int pos, symbol_t name);
ast_expr_t ast_this_expr(int pos);
ast_expr_t ast_class_init_expr(int pos, symbol_t name);
ast_expr_t ast_arr_init_expr(int pos, ast_type_t type, ast_expr_t size);
ast_expr_t ast_arr_decl_expr(int pos, list_t arr_decl);
ast_expr_t ast_int_expr(int pos, int val);
ast_expr_t ast_float_expr(int pos, float val);
ast_expr_t ast_char_expr(int pos, char val);
ast_expr_t ast_string_expr(int pos, string_t val);
ast_expr_t ast_bool_expr(int pos, bool val);

void print_ast_expr(ast_expr_t r, int level);
void print_ast_op_expr(ast_expr_t r, int level);
void print_ast_not_expr(ast_expr_t r, int level);
void print_ast_arr_access_expr(ast_expr_t r, int level);
void print_ast_method_access_expr(ast_expr_t r, int level);
void print_ast_arr_length_expr(ast_expr_t r, int level);
void print_ast_idf_expr(ast_expr_t r, int level);
void print_ast_this_expr(ast_expr_t r, int level);
void print_ast_class_init_expr(ast_expr_t r, int level);
void print_ast_arr_init_expr(ast_expr_t r, int level);
void print_ast_arr_decl_expr(ast_expr_t r, int level);
void print_ast_int_expr(ast_expr_t r, int level);
void print_ast_float_expr(ast_expr_t r, int level);
void print_ast_char_expr(ast_expr_t r, int level);
void print_ast_string_expr(ast_expr_t r, int level);
void print_ast_bool_expr(ast_expr_t r, int level);

struct ast_type_s
{
    enum
    {
        AST_PRIMITIVE_TYPE,
        AST_CLASS_TYPE,
        AST_ARRAY_TYPE,
    } kind;
    int pos;
    union
    {
        ast_primitive_type_t primitive;
        symbol_t class_;
		ast_primitive_type_t array;
    } u;
};
ast_type_t ast_primitive_type(int pos, ast_primitive_type_t primitive);
ast_type_t ast_class_type(int pos, symbol_t class_name);
ast_type_t ast_array_type(int pos, ast_primitive_type_t primitive);

void print_ast_type(ast_type_t r, int level);
void print_ast_primitive_type(ast_type_t r, int level);
void print_ast_class_type(ast_type_t r, int level);
void print_ast_array_type(ast_type_t r, int level);

void print_filter(int level);

#endif
